package com.gitee.l0km.beanfilter.interceptor;

import static com.google.common.base.MoreObjects.firstNonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.beanfilter.annotations.FilterMeta;
import com.gitee.l0km.beanfilter.core.CodecPhase;
import com.gitee.l0km.beanfilter.core.CodecScope;
import com.gitee.l0km.beanfilter.core.IFieldFilter;
import com.gitee.l0km.beanfilter.core.IFilter;
import com.gitee.l0km.beanfilter.core.IValueFilter;
import com.gitee.l0km.beanfilter.core.SimpleValueFilter;
import com.gitee.l0km.beanfilter.core.StringCoverFilter;
import com.gitee.l0km.beanfilter.core.utils.CaseSupport;
import com.gitee.l0km.casban.ctor.AnnotConstructors;
import com.gitee.l0km.casban.ctor.AnnotConstructors.CtorArgInfo;
import com.gitee.l0km.casban.ctor.AnnotConstructors.CtorArgStrategy;
import com.gitee.l0km.common.spring.core.annotation.MergedAnnotation;
import com.gitee.l0km.common.spring.core.annotation.TypeMappedAnnotation;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimaps;

class BaseContainer <F extends IFilter>{

	private static final String FIELD_BEAN_CLASS = "beanClass";
	private static final String FIELD_CORE = "scope";
	private final Class<F> filterClass;
	protected Set<FilterMetadata<F>> metadatas = new LinkedHashSet<>(); 
	protected BaseContainer(Class<F> filterClass) {
		this.filterClass = filterClass;
	}
    protected void add(FilterMetadata<F> meta) {
    	metadatas.add(meta);
    }
    /**
     * {@link #metadatas}改为不可修改对象
     */
    void immutable() {
    	metadatas = Collections.unmodifiableSet(metadatas);
    }
    /**
	 * 如果注解定义的Java Bean类型({@code beanClass}字段)有对应的装饰类型( {@link com.gitee.l0km.xthrift.base.ThriftDecorator} ),
	 * 且过滤编解码目标范围有 {@link CodecScope#THRIFT}, 则为装饰类型创建过滤器实例
	 * @param mergedAnnotation
	 * @throws ReflectiveOperationException
	 * @see {@link #addFilter(MergedAnnotation, FilterMeta, Function)}
	 */
	private void addDecoratorFilter(MergedAnnotation<FilterMeta> mergedAnnotation) throws ReflectiveOperationException {
		FilterMeta annot = mergedAnnotation.synthesize();
		Class<?> decoratorClass;
		if (null != (decoratorClass = ThriftDecoratorSupport.getThriftDecoratorOf(annot.beanClass()))
				&& CodecScope.THRIFT.match(annot.scope())) {
			Map<String, Object> attrs = new LinkedHashMap<>(mergedAnnotation.asMap());
			/** 替换 beanClass,core字段 */
			attrs.replace(FIELD_BEAN_CLASS, decoratorClass);
			attrs.replace(FIELD_CORE, CodecScope.THRIFT);
			FilterMeta decoratorMeta = TypeMappedAnnotation.of(FilterMeta.class, attrs).synthesize();
			addFilter(mergedAnnotation, decoratorMeta, argInfo -> {
				/** 替换 beanClass,core字段 */
				argInfo.argsMap.replace(FIELD_BEAN_CLASS, decoratorClass);
				argInfo.argsMap.replace(FIELD_CORE, CodecScope.THRIFT);
				return argInfo;
			});
		}
	}

	/**
	 * 根据注解创建过滤器<br>
	 * @param mergedAnnotation
	 * @param synthesized 合成注解对象，为{@code null}则从{@code mergedAnnotation}合成
	 * @param modifier 用于修改造参数，为{@code null}忽略
	 * @throws ReflectiveOperationException
	 * @see {@link AnnotConstructors#construct(MergedAnnotation, Function, Function)}
	 */
	public void addFilter(MergedAnnotation<FilterMeta> mergedAnnotation, FilterMeta synthesized,
			Function<CtorArgInfo, CtorArgInfo> modifier) throws ReflectiveOperationException {
		Object instance = AnnotConstructors.construct(mergedAnnotation, CtorArgStrategy.UNTIL_ROOT, modifier);
		FilterMeta annot = null == synthesized ? mergedAnnotation.synthesize() : synthesized;
		if (filterClass.isInstance(instance)) {
			add(FilterMetadata.of(annot, instance));
		}
	}
	public void addFilter(Iterable<MergedAnnotation<FilterMeta>> mergedAnnotations) throws ReflectiveOperationException {
		if(null != mergedAnnotations) {
			for(MergedAnnotation<FilterMeta> mergedAnnotation:
					FluentIterable.from(mergedAnnotations).filter(Predicates.notNull())) {
				addFilter(mergedAnnotation, null, null);
				addDecoratorFilter(mergedAnnotation);
			}
		}
	}

	public boolean isEmpty() {
		return metadatas.isEmpty();
	}
	ImmutableSet<FilterMetadata<F>> getMetadatas(CodecPhase phase,CodecScope scope){
		CodecPhase _phase = firstNonNull(phase, CodecPhase.BOTH);
		CodecScope _scope = firstNonNull(scope, CodecScope.ALL);
		return FluentIterable.from(metadatas)
			.filter(meta->_phase.match(meta.phase) && _scope.match(meta.scope))
			.toSet();
	}
	ImmutableSet<F> getFilters(CodecPhase phase,CodecScope scope){
		CodecPhase _phase = firstNonNull(phase, CodecPhase.BOTH);
		CodecScope _scope = firstNonNull(scope, CodecScope.ALL);
		return FluentIterable.from(metadatas)
				.filter(meta->_phase.match(meta.phase) && _scope.match(meta.scope))
				.transform(meta->meta.filter)
				.toSet();
	}
	boolean isEmpty(CodecPhase phase,CodecScope scope) {
		return getMetadatas(phase,scope).isEmpty();
	}
	private static Class<?>  findAssignableClass(Collection<Class<?>> collection, Class<?> clazz, boolean superRequired) {
		Class<?> v = null;
		if(null!=collection && null != clazz) {
			Predicate<Class<?>> srp= (c)->!superRequired;
			v = collection.stream()
					.filter(srp.or(Predicate.isEqual(clazz).negate()) )
					.filter(c->c.isAssignableFrom(clazz)).findFirst().orElse(null);
		}
		return v;
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("BaseContainer [filterClass=").append(filterClass).append(", metadatas=").append(metadatas)
				.append("]");
		return builder.toString();
	}
	static class FieldFilterContainer  implements IFieldFilter{
		private final ImmutableListMultimap<Class<?>, FilterMetadata<IFieldFilter>> metadatas;
		public FieldFilterContainer(Set<FilterMetadata<IFieldFilter>> metadatas) {
			this.metadatas = Multimaps.index(metadatas, m->m.beanClass);
		}
		public Set<Class<?>> associateClasses(){
			return metadatas.keySet();
		}
		public boolean isEmpty() {
			return metadatas.isEmpty();
		}
		
		private List<FilterMetadata<IFieldFilter>> getMetadatas(Class<?> clazz){
			if(null != clazz) {
				Collection<FilterMetadata<IFieldFilter>> meta = metadatas.get(clazz);
				if(meta.isEmpty()) {
					Class<?> matchedClass = findAssignableClass(metadatas.keySet(),clazz, false);
					if(matchedClass != null) {
						meta = metadatas.get(matchedClass);
					}
				}
				return meta.stream().collect(Collectors.toCollection(ArrayList::new));
			}
			return Collections.emptyList();
		}
		@Override
		@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
		public boolean permit(Class<?> clazz, String fieldName) {
			List<FilterMetadata<IFieldFilter>> metadatas = getMetadatas(clazz);
			for(FilterMetadata<IFieldFilter> meta:metadatas) {
				if(!meta.filter.permit(clazz, fieldName)) {
					return false;
				}
			}
			return true;
		}
		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			builder.append("FieldFilterContainer [filters=").append(metadatas).append("]");
			return builder.toString();
		}
	}
	static class ValueFilterContainer  implements IValueFilter{
		private static final String EMPTY_STR = "";
		private final HashBasedTable<Class<?>, String, FilterMetadata<IValueFilter>> table = HashBasedTable.create();
		
		public ValueFilterContainer() {
		}
		public ValueFilterContainer(Set<FilterMetadata<IValueFilter>>metadatas) {
			if(null != metadatas) {
				for(FilterMetadata<IValueFilter> meta:metadatas) {
					combine(meta);
				}
			}
		}
		public Set<Class<?>> associateClasses(){
			return ImmutableSet.copyOf(table.rowKeySet());
		}
		public boolean isEmpty() {
			return table.isEmpty();
		}
		@SafeVarargs
		final ValueFilterContainer combine(FilterMetadata<IValueFilter>... metadatas) {
			if(null != metadatas) {
				for(FilterMetadata<IValueFilter> meta : metadatas) {
					if(meta.filter instanceof SimpleValueFilter) {
						SimpleValueFilter simpleValueFilter = (SimpleValueFilter)meta.filter;
						for(String name:simpleValueFilter.getConstValues().keySet()) {
							table.put(meta.beanClass, name, meta);
						}
					}else if(meta.filter instanceof StringCoverFilter) {
						table.put(meta.beanClass, ((StringCoverFilter)meta.filter).getFieldName(), meta);
					} else if(meta.filter instanceof ValueFilterContainer) {
						this.table.putAll(((ValueFilterContainer)meta.filter).table);
					}else if(null != meta){
						table.put(meta.beanClass, EMPTY_STR, meta);
					}
				}
			}
			return this;
		}
		private FilterMetadata<IValueFilter> getMeta(Class<?> clazz,String name,boolean autoCase){
			FilterMetadata<IValueFilter> meta = table.get(clazz, name);
			if(null == meta && autoCase) {
				if(CaseSupport.isCamelcase(name)) {
					return getMeta(clazz,CaseSupport.toSnakecaseIfCamel(name),false);
				}else if(CaseSupport.isSnakecase(name)) {
					return getMeta(clazz,CaseSupport.toCamelcase(name),false);
				}
			}
			return meta;
		}
		@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
		private FilterMetadata<IValueFilter> getMeta(Class<?> clazz,String name){
			if(null != clazz && null != name) {
				FilterMetadata<IValueFilter> meta = getMeta(clazz, name,true);
				if(null == meta) {
					Class<?> matchedClass = findAssignableClass(table.rowKeySet(),clazz, true);
					if(matchedClass != null) {
						meta = getMeta(matchedClass, name,true);
						if(null == meta && !name.isEmpty()) {
							meta = table.get(clazz, EMPTY_STR);
						}
					}
				}
				return meta;
			}
			return null;
		}
		
		@Override
		public Object process(Class<?> clazz,String name, Object value) {
			FilterMetadata<IValueFilter> meta;
			if(null  != (meta = getMeta(clazz,name))) {
				return meta.filter.process(clazz, name, value);
			}
			return value;
		}
		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			builder.append("ValueFilterContainer [table=").append(table).append("]");
			return builder.toString();
		}
	}
	static class FilterMetadata <F extends IFilter>{
		final CodecPhase phase;
		final CodecScope scope;
		final Class<?> beanClass;
		final F filter;
		private FilterMetadata(CodecPhase phase, CodecScope scope, Class<?> beanClass, F filter) {
			this.phase = phase;
			this.scope = scope;
			this.beanClass = beanClass;
			this.filter = filter;
		}

		static <F extends IFilter>  FilterMetadata<F> of(CodecPhase phase,CodecScope scope,Class<?> beanClass,F filter) {
			return new FilterMetadata<>(phase,scope, beanClass,(F)filter);
		}
		
		@SuppressWarnings("unchecked")
		static <F extends IFilter>  FilterMetadata<F> of(FilterMeta meta, Object filter) {
			return new FilterMetadata<>(meta.phase(),meta.scope(), meta.beanClass(),(F)filter);
		}
		@Override
		public int hashCode() {
			return Objects.hash(beanClass, filter, phase, scope);
		}
		@SuppressWarnings("rawtypes")
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			FilterMetadata other = (FilterMetadata) obj;
			return Objects.equals(beanClass, other.beanClass) && Objects.equals(filter, other.filter)
					&& phase == other.phase && scope == other.scope;
		}
		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			builder.append("FilterMetaaata [phase=").append(phase).append(", scope=").append(scope)
					.append(", beanClass=").append(beanClass).append(", filter=").append(filter).append("]");
			return builder.toString();
		}
	}
}
